#include "interboursier.h"

/**
 * Permet de démarrer un serveur interboursier
 * @param mdl module lié au serveur interboursier
 * @param port port sur lequel le serveur se bind
 * ATTENTION: le port du serveur d'exécution doit être celui de son serveur interboursier+1
 */
void launch_interboursier(module* mdl, const int port) {
	id2port* routing_table =( id2port*) malloc(sizeof(id2port));
	server srv = *(mdl->srv);
	strcpy(srv.name, mdl->name);
	srv.my_port=port;
	srv.isAlive = 1;
	if ((srv.sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}
	srv.local_addr.sin_family = AF_INET; /* host byte order */
	srv.local_addr.sin_port = htons(port); /* short, network byte order */
	srv.local_addr.sin_addr.s_addr = INADDR_ANY; /* auto-remplissage avec mon IP */
	bzero(&(srv.local_addr.sin_zero), 8); /* zero pour le reste de struct */

	if (bind(srv.sockfd,( struct sockaddr *)&(srv.local_addr),sizeof(struct sockaddr))== -1) {
		perror("bind");
		exit(1);
	}

	if (listen(srv.sockfd, 5) == -1) { // on choisit un nombre max de connexions = 5
		perror("listen");
		exit(1);
	}
	char tmp[128];
	sprintf(tmp,"créé.");
	log_srv(&srv,tmp);
	if(!fork()) { /* fils première génération - permet de revenir de la méthode par contre créé des zombies */
		while(1) { // boucle d' accept() pour recevoir les connexions
			srv.sin_size = sizeof(struct sockaddr_in);
			int new_fd; // descripteur lié au socket de la connexion courante
			if ((new_fd = accept(srv.sockfd, (struct sockaddr *)&(srv.remote_addr),
							&(srv.sin_size))) == -1) {
				perror("accept");
				continue;
			}
			if(!srv.isAlive) {
				close(new_fd); //si une connexion arrive alors que le programme est terminé, on sort
				break; return;
			}
			sprintf(tmp, "connexion entrante depuis %s:%d",inet_ntoa(srv.remote_addr.sin_addr),ntohs(srv.remote_addr.sin_port));
			log_srv(&srv,tmp);
			if (!fork()) { /* fils seconde génération - permet d'être multiclient */
				if(!srv.isAlive) {
					close(new_fd); //si une connexion arrive alors que le programme est terminé, on sort
					return;
				}
				char answer[MAXRECVDATA];
				/*sprintf(answer,"Serveur %s.\n", srv.name);
				if (send(new_fd, answer, strlen(answer), 0) == -1)
				perror("send");*/

				srv.recvdata = (msg*) malloc (sizeof (msg));
				char recvdata[MAXRECVDATA];
				bzero(srv.recvdata,sizeof (msg));
				if ((srv.numbytes=recv(new_fd, recvdata, MAXRECVDATA, 0)) == -1) {
					perror("recv");
					log_srv(&srv,"erreur lors du recv!");
					exit(1);
				}
				if(strlen(recvdata)==9) { // alors c'est un message d'identification de la forme: 0001:1201
					char tmp1[128], tmp2[128];
					strncpy(tmp1,recvdata,4); tmp1[4]='\0';
					strncpy(tmp2,strstr(recvdata,":")+1,4); tmp2[4]='\0';
					add_route(routing_table,atoi(tmp1),atoi(tmp2));
					sprintf(answer,"Confirmation d'autentification: bourse %d port %d",atoi(tmp1),route(routing_table,atoi(tmp1)));
					if (send(new_fd, answer, strlen(answer), 0) == -1)
					perror("Erreur lors de l'envoi de la confirmation d'autentification du serveur d'acquisition");
					else log_srv(&srv,answer);
					close(new_fd);
					exit(1); // on sort juste après le traitement
				}
				srv.recvdata = str_to_msg(recvdata);
				if(srv.recvdata==NULL) {
					log_srv(&srv,"réception d'un message non valide");
					close(new_fd);
				}
				sprintf(tmp,"%d octets reçu par %s %s:%d",srv.numbytes,srv.name, inet_ntoa(srv.remote_addr.sin_addr),ntohs(srv.remote_addr.sin_port));
				log_msg(tmp,srv.recvdata);
				//msg * rtn = (msg*) malloc(sizeof(msg)); // message retourné par process_msg
				char idbourse[5]; // identifiant de la bourse
				// TODO d'abord on regarde le type de message:
				strncpy(idbourse,srv.recvdata->action,4); // on copie l'id de la bourse du message
				idbourse[4]='\0';
				int remote_port = route(routing_table,atoi(idbourse));
				if(remote_port==-1) {
					sprintf(answer,"Erreur: la bourse %d n'est pas en ligne!",atoi(idbourse));
					log_srv(&srv,answer);
					char tmptype[11]; sprintf(tmptype,"Accuse%s",srv.recvdata->type);
					msg* answered = create_msg(srv.recvdata->emetteur,tmptype,srv.recvdata->action,"0");
					sprintf(answer,"%s",msg_to_str(answered));
					if (send(new_fd, answer, strlen(answer), 0) == -1)
						perror("Erreur lors de l'envoi de l'erreur concernant la bourse non connectée");
					close(new_fd);
					exit(1);
				}

				int ack_sockfd;
				struct sockaddr_in exec_addr; /* adresse du serveur d'exécution */
				struct hostent *he;

				if ((he=gethostbyname("localhost")) == NULL) {
					herror("gethostbyname");
					exit(1);
				}

				if ((ack_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
					perror("socket");
					exit(1);
				}

				exec_addr.sin_family = AF_INET; /* host byte order */
				exec_addr.sin_port = htons(remote_port); /* short, network byte order */
				exec_addr.sin_addr = *((struct in_addr *)he->h_addr);
				bzero(&(exec_addr.sin_zero), 8); /* zero pour le reste de struct */
				log_smth("envoi des données vers le serveur d'acquisition sur %s:%d",inet_ntoa(exec_addr.sin_addr),ntohs(exec_addr.sin_port));

				if (connect(ack_sockfd,( struct sockaddr *)&(exec_addr), sizeof(struct sockaddr)) == -1) {
					sprintf(tmp,"%s erreur de connexion",srv.name);
					perror(tmp);
					log_srv(&srv,"déjà connecté ou impossible de se connecter au serveur d'acquisition");
					exit(1);
				}

				if (send(ack_sockfd, recvdata, strlen(recvdata), 0) == -1) {
					sprintf(tmp,"%s erreur d'envoi de message vers le serveur d'acquisition",srv.name);
					perror(tmp);
					log_srv(&srv, "erreur lors de l'envoi du message vers le serveur d'acquisition");
					exit(1);
				}
				// on attend désormais la réponse du serveur d'exécution
				bzero(srv.recvdata,sizeof (msg)); srv.numbytes=0;
				if (srv.isAlive && (srv.numbytes=recv(ack_sockfd, recvdata, MAXRECVDATA, 0)) == -1) {
					perror("recv");
					log_srv(&srv,"erreur lors du recv!");
					sprintf(tmp,"Erreur lors de la réception de la confirmation du serveur d'acquisition. Au revoir.\n");
					send(new_fd, tmp, strlen(tmp), 0);
					close(new_fd);
					exit(1);
				}
				srv.recvdata = str_to_msg(recvdata);
				sprintf(tmp,"%d octets reçu par %s %s:%d",srv.numbytes,srv.name, inet_ntoa(srv.remote_addr.sin_addr),ntohs(srv.remote_addr.sin_port));
				log_msg(tmp,srv.recvdata);
				close(ack_sockfd); // on ferme la connexion avec le serveur d'exécution
				// puis on renvoit cette réponse au terminal de base
				if (send(new_fd, recvdata, strlen(recvdata), 0) == -1) {
					sprintf(tmp,"%s erreur d'envoi du message vers le serveur d'acquisition",srv.name);
					perror(tmp);
					log_srv(&srv, "erreur lors de l'envoi du message vers le serveur d'acquisition");
					exit(1);
				}

				close(new_fd); // on ferme la connexion une fois le message reçu et renvoyé
			}
			while(waitpid(-1,NULL,WNOHANG) > 0); // nettoyage des processus fils
			close(new_fd);

		} // fin while du "accept"
	} // fin fils première génération


}

int interboursier_msg(msg *mess) {
	return interboursier(mess->emetteur, mess->type, mess->action, mess->valeur);
}

/**
 * Interboursier reçoit des messages et les analyse afin de savoir vers quel serveur d'acquisition les diriger.
 */
int interboursier(char* emetteur, char* type_transaction,
		char* id_action_ordre, char* quantite_ordre) {

	int i;
	id2port *tbl =( id2port*) malloc(sizeof(id2port));
	//lien dans un struct des identifiants de bourse avec le port associé
	//	strcpy(tbl->id_bourse[0],"0001"); tbl->port_bourse[0]=1200;
	//	strcpy(tbl->id_bourse[1],"0002"); tbl->port_bourse[1]=1200;
	//	strcpy(tbl->id_bourse[2],"0003"); tbl->port_bourse[2]=1200;
	//	strcpy(tbl->id_bourse[3],"0004"); tbl->port_bourse[3]=1200;

	//On regarde si le message est un ordre ou un accusé
	if (type_transaction[2] == 'c') { //c = 3è lettre de Accusé
		//Si c'est un accusé, on analyse les 4 premiers chiffres de l'emetteur
		//(banque où se trouve le commerçant).
		char bourse_origine[100];
		for (i = 0; i < 4; i++) {
			strncat(bourse_origine, emetteur + i, 1);
		}
		printf("bourse d'origine: %s", bourse_origine);
	}
	//Si c'est un ordre, on analyse les 4 premiers chiffres de l'identifiant
	//de l'action (banque où est cotée l'action).
	else {
		char bourse_cotation[100];
		for (i = 0; i < 4; i++) {
			strncat(bourse_cotation, id_action_ordre + i, 1);
		}
		//printf("bourse de cotation:%s\n", bourse_cotation);
		//printf("bourse %s, port %d",bourse_cotation,route(tbl,bourse_cotation));
		return route(tbl,atoi(bourse_cotation));
	}
	return -1;
}

/**
 * Permet de récupérer un socket descriptor à partir de l'identifiant d'une bourse
 */
int route(id2port* tbl, int id) {
	int fd=-1;
	int i=0;
	//4 car 4 bourses
	for (i=0; i<NB_BOURSES; i++) {
		if (tbl->id_bourse[i]==id) {
			fd=tbl->port_bourse[i];
			break;
		}
	}
	return fd;
}

int add_route(id2port* tbl, int id, int port) {
	static int position=0; // position dans le tableau
	if (position > NB_BOURSES)
		return 0; // plus de place
	if(route(tbl,id)!=-1) return 1; // le serveur est déjà autentifié
	tbl->id_bourse[position]=id;
	tbl->port_bourse[position++]=port;
	return 1;
}
